iT邦幫忙

2024 iThome 鐵人賽

DAY 2
0
Modern Web

前進React 生態系 : 技術應用與概念解析系列 第 2

Day 02 - 泛起來 ~ 讓泛型寫出更靈活的程式碼

  • 分享至 

  • xImage
  •  

什麼是泛型?

首先先看一個簡單的例子

function identity<T>(arg: T): Type {
  return arg;
}
const stringOutput = identity<string>("Hello World");  
const numberOutput = identity<number>(25);

這是一個泛型函式,主要由兩個部分構成:

  1. Type Parameter: 這邊是用 T,可以使用任意名稱。但通常習慣使用 T 表示,代表 "Type" 。這個參數允許我們在撰寫程式時保持型別彈性。

除了 T 以外,以下是一些常見的泛型參數:

  • U:當你需要多個泛型參數時,會使用 U 來表示第二個型別參數。
  • K(Key):物件鍵值的泛型
  • V(Value):表示與 K 配對的值的型別。這個泛型參數通常與 K 搭配,定義物件中鍵對應的值的型別。
  1. Type Argument : 泛型函式使用時,我們將具體的型別傳入函式中,例如 stringnumber,使得程式碼可以適應不同型別的需求。

為什麼要用泛型?

  1. 安全性 : 雖然用 any 可以解決部份問題,但泛型讓我們可以編寫適用於不同類型數據的代碼,仍然保持 TypeScript 的類型安全性,避免了丟失型別檢查的風險。
  2. 可重用性:泛型可以撰寫適用於多種型別的程式碼,避免為每個型別重複撰寫不同的函式。
  3. 可維護性: 因為寫的 code 更少,也比較容易維護。

用 extends 將泛型做限制

我們可以透過 extends 將泛型限制於某些特定型別。舉個例子,如果我們想限制 identity 函式只能接受 numberstring

function identity<T extends number | string>(arg: T): T {
  return arg;
}
const stringOutput = identity<string>("Hello World"); 
const numberOutput = identity<number>(25);

這樣我們就限制了 T 必須是 numberstring 型別。

extends 除了限制型別外,也可以用來確保傳入的物件具備特定屬性,會在接下來的例子說明。

React 和泛型結合的應用

共用元件

接下來,以一個列表(List)元件為例,展示如何在 React 中使用泛型來定義元件的 props 型別:

type ListProps<T> = {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
};

function List<T extends { id: string | number }>({
  items,
  renderItem,
}: ListProps<T>) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

在這個例子中,List 元件接受一個 items 陣列,其元素型別由泛型 T 決定,並透過 renderItem 函數來渲染每個元素。使用泛型讓這個元件可以處理各種型別的資料,並確保每個資料項目都有 id 屬性。

Custom Hook

除了元件外,泛型也常用於自訂 Hook 的型別定義。

以 useFetch 為例

import { useState, useEffect } from "react";

type FetchState<T> = {
  data: T | null;
  error: Error | null;
  loading: boolean;
};

export function useFetch<T>(url: string): FetchState<T> {
  const [state, setState] = useState<FetchState<T>>({
    data: null,
    error: null,
    loading: true,
  });

  useEffect(() => {
    const fetchData = async () => {
      setState({ data: null, error: null, loading: true });
      try {
        const response = await fetch(url);
        const data = await response.json();
        setState({ data, error: null, loading: false });
      } catch (error) {
        setState({
          data: null,
          error: error instanceof Error ? error : new Error("Unknown error"),
          loading: false,
        });
      }
    };

    fetchData();
  }, [url]);

  return state;
}

這邊使用了泛型,因為 fetch 的回傳值可能會是不同的型別。

補充: 以上程式碼都可以再更優化,這邊只是讓大家了解泛型帶來的好處。未來怎麼優化可以參考之後的文章。

希望透過以上的分享能讓大家更了解泛型。
至於許多常見的 Utility Types 等都是針對以上概念去做延伸。

參考資料:
https://www.typescriptlang.org/docs/handbook/2/generics.html


上一篇
Day 01 - 前言
下一篇
Day 03 - 更複雜的泛型,那些好用的關鍵字和 Utility Types
系列文
前進React 生態系 : 技術應用與概念解析22
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言